package world;

import java.nio.FloatBuffer;

import opengl.program.GLProgramManager;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;

public class ChunkLayer {
	public final static int SIZE = Chunk.SIZE;

	private int indexX, indexY, indexZ;
	private float baseX, baseY, baseZ;

	private FloatBuffer verticesBuffer;
	private ChunkBlock[][][] blocks = null;
	private boolean[][][] renderableMatixes = new boolean[SIZE][SIZE][SIZE];
	private byte[][][] renderMatixes = new byte[SIZE][SIZE][SIZE];
	private int totalBlockFaces;

	private World world;

	public ChunkLayer(World world, int indexX, int indexY, int indexZ) {
		this.world = world;
		this.indexX = indexX;
		this.indexZ = indexZ;
		this.indexY = indexY;
		this.baseX = indexX * SIZE;
		this.baseZ = indexZ * SIZE;
		this.baseY = indexY * SIZE;
	}

	public void setBlocks(ChunkBlock[][][] blocks) {
		this.blocks = blocks;
	}

	public int hashCode() {
		return new ChunkCoord2D(indexX, indexZ).hashCode();
	}

	public ChunkBlock findBlock(int x, int y, int z) {
		if (y > SIZE || x > SIZE || z > SIZE) {
			return null;
		}

		return blocks[x][y][z];
	}

	public void buildRenderData() {
		int maxIndex = SIZE - 1;
		for (int i = 0; i <= maxIndex; i++) {
			for (int j = 0; j <= maxIndex; j++) {
				for (int k = 0; k <= maxIndex; k++) {
					ChunkBlock b = blocks[i][j][k];
					renderableMatixes[i][j][k] = b.isRenderable();
				}
			}
		}

		totalBlockFaces = 0;
		for (int i = 0; i <= maxIndex; i++) {
			for (int j = 0; j <= maxIndex; j++) {
				for (int k = 0; k <= maxIndex; k++) {
					byte renderBits = 0b00000000;
					if (i == 0 || !renderableMatixes[i - 1][j][k]) {
						renderBits = (byte) (renderBits | BlockFaceEnum.LEFT.getBitPostion());
						totalBlockFaces++;
					}

					if (i == maxIndex || !renderableMatixes[i + 1][j][k]) {
						renderBits = (byte) (renderBits | BlockFaceEnum.RIGHT.getBitPostion());
						totalBlockFaces++;
					}

					if (j > 0 && !renderableMatixes[i][j - 1][k]) {// if the block is at the most bottom, we do not render it
						renderBits = (byte) (renderBits | BlockFaceEnum.BOTTOM.getBitPostion());
						totalBlockFaces++;
					}

					if (j == maxIndex || !renderableMatixes[i][j + 1][k]) {
						renderBits = (byte) (renderBits | BlockFaceEnum.TOP.getBitPostion());
						totalBlockFaces++;
					}

					if (k == 0 || !renderableMatixes[i][j][k - 1]) {
						renderBits = (byte) (renderBits | BlockFaceEnum.BACK.getBitPostion());
						totalBlockFaces++;
					}

					if (k == maxIndex || !renderableMatixes[i][j][k + 1]) {
						renderBits = (byte) (renderBits | BlockFaceEnum.FRONT.getBitPostion());
						totalBlockFaces++;
					}
					renderMatixes[i][j][k] = renderBits;
				}
			}
		}

		verticesBuffer = BufferUtils.createFloatBuffer(totalBlockFaces * 6 * Vertex.SIZE_ALL);// 6 faces 6 vertexes/face

		for (int i = 0; i <= maxIndex; i++) {
			for (int j = 0; j <= maxIndex; j++) {
				for (int k = 0; k <= maxIndex; k++) {
					ChunkBlock b = blocks[i][j][k];
					byte renderBits = renderMatixes[i][j][k];
					if (renderBits <= 0) {
						continue;
					}
					appendVertexToBuffer(verticesBuffer, b.getVertexes(), renderBits);
				}
			}
		}

		blocks = new ChunkBlock[SIZE][SIZE][SIZE];// clear out

		verticesBuffer.flip();
	}

	private void appendVertexToBuffer(FloatBuffer buffer, Vertex[] vertexes, byte renderBits) {
		for (BlockFaceEnum blockFace : BlockFaceEnum.values()) {
			if ((renderBits & blockFace.getBitPostion()) <= 0) {
				continue;
			}

			int baseIndex = ChunkBlock.VERTEX_PER_FACE * blockFace.getID();
			for (int i = 0; i < ChunkBlock.VERTEX_PER_FACE; i++) {
				buffer.put(vertexes[baseIndex + i].getElements());
			}
		}
	}

	public void render() {
		GL20.glUniform4f(GLProgramManager.get().getBlockProgram().getBasePositionUniform(), baseX, baseY, baseZ, 0.0f);

		// Bind to the VAO that has all the information about the vertices
		GL20.glEnableVertexAttribArray(0);
		GL20.glEnableVertexAttribArray(1);
		GL20.glEnableVertexAttribArray(2);

		renderBuffer();

		GL20.glDisableVertexAttribArray(0);
		GL20.glDisableVertexAttribArray(1);
		GL20.glDisableVertexAttribArray(2);
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
	}

	private void renderBuffer() {
		int vboId = GL15.glGenBuffers();
		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboId);
		GL15.glBufferData(GL15.GL_ARRAY_BUFFER, verticesBuffer, GL15.GL_STATIC_DRAW);

		GL20.glVertexAttribPointer(0, Vertex.SIZE_POSITION, GL11.GL_FLOAT, false, Vertex.STRIDE, Vertex.OFFSET_POSITION);
		GL20.glVertexAttribPointer(1, Vertex.SIZE_COLOR, GL11.GL_BYTE, false, Vertex.STRIDE, Vertex.BYTE_SIZE_COLOR);
		GL20.glVertexAttribPointer(2, Vertex.SIZE_TEXTURE, GL11.GL_FLOAT, false, Vertex.STRIDE, Vertex.OFFSET_TEXTURE);

		GL13.glActiveTexture(GL13.GL_TEXTURE0);
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, Blocks.DIRT.getID());
		
/*		for (int i = 0; i < totalBlockFaces; i ++) {
			GL11.glBindTexture(GL11.GL_TEXTURE_2D, Blocks.get(i%3+1).getID());
			if(i<1000){
				GL11.glBindTexture(GL11.GL_TEXTURE_2D, Blocks.STONE.getID());
			}else if(i<2000){
				GL11.glBindTexture(GL11.GL_TEXTURE_2D, Blocks.DIRT.getID());
			}else{
				GL11.glBindTexture(GL11.GL_TEXTURE_2D, Blocks.GRASS.getID());
			}

			GL11.glDrawArrays(GL11.GL_QUADS, i*4, 4 );
		}
*/		
		
		GL11.glDrawArrays(GL11.GL_QUADS, 0, totalBlockFaces*6 );

		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
		GL15.glDeleteBuffers(vboId);// this is very important to release the memory
	}
}
